"use strict"

import { IJcObject } from '../common/Constracts';
import { JcDebugger } from './Main';
import { ClassInfo } from './JcDebugInfo';
import { ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_NATIVE, ACC_ABSTRACT } from './JcDebugInfo';
import { strendswith } from '../common/JcUtilLib';
import { sprintf } from "sprintf-js"

const TYPE_CODE_VOID = 0
const TYPE_CODE_BYTE = 1
const TYPE_CODE_BOOLEAN = 2
const TYPE_CODE_SHORT = 3
const TYPE_CODE_INT = 4
const TYPE_CODE_BYTE_ARRAY = 5
const TYPE_CODE_BOOLEAN_ARRAY = 6
const TYPE_CODE_SHORT_ARRAY = 7
const TYPE_CODE_INT_ARRAY = 8
const TYPE_CODE_OBJECT_ARRAY = 9
const TYPE_CODE_OBJECT_REF = 10

const OBJ_TYPE_OBJECT = 0;
const OBJ_TYPE_BOOLEAN = 10;
const OBJ_TYPE_BYTE = 11;
const OBJ_TYPE_SHORT = 12;
const OBJ_TYPE_INT = 13;
const OBJ_TYPE_REF = 14;


export class JavaType {
    public __typename: string;
    public __code: number;

    public constructor(descriptor: string) {
        if (descriptor == "B") {
            this.__typename = "byte";
            this.__code = TYPE_CODE_BYTE;
        }
        else if (descriptor == "Z") {
            this.__typename = "boolean";
            this.__code = TYPE_CODE_BOOLEAN;
        }
        else if (descriptor == "S") {
            this.__typename = "short";
            this.__code = TYPE_CODE_SHORT;
        }
        else if (descriptor == "I") {
            this.__typename = "int";
            this.__code = TYPE_CODE_INT;
        }
        else if (descriptor == "[B") {
            this.__typename = "byte[]";
            this.__code = TYPE_CODE_BYTE_ARRAY;
        }
        else if (descriptor == "[Z") {
            this.__typename = "boolean[]";
            this.__code = TYPE_CODE_BOOLEAN_ARRAY;
        }
        else if (descriptor == "[S") {
            this.__typename = "short[]";
            this.__code = TYPE_CODE_SHORT_ARRAY;
        }
        else if (descriptor == "[I") {
            this.__typename = "int[]";
            this.__code = TYPE_CODE_INT_ARRAY;
        }
        else if (descriptor.substr(0, 2) == "[L") {
            this.__typename = descriptor.substring(2, descriptor.length - 2) + "[]";
            this.__code = TYPE_CODE_OBJECT_ARRAY;
        }
        else if (descriptor[0] == "L") {
            this.__typename = descriptor.substring(1, descriptor.length - 2);
            this.__code = TYPE_CODE_OBJECT_REF;
        }
        else {
            throw "descriptor error";
        }
    }

    public typename(): string {
        return this.__typename
    }

    public shortname(): string {
        if (this.__code == TYPE_CODE_OBJECT_ARRAY || this.__code == TYPE_CODE_OBJECT_REF) {
            return this.__typename.split("/").pop();
        }
        else {
            return this.__typename;
        }
    }
    public code(): number {
        return this.__code;
    }

    public value_size(): number {
        if (this.__code == TYPE_CODE_BYTE || this.__code == TYPE_CODE_BOOLEAN) {
            return 1;
        }
        else if (this.__code == TYPE_CODE_INT) {
            return 4;
        }
        else {
            return 2;
        }
    }

    public is_array(): boolean {
        return this.__code == TYPE_CODE_BYTE_ARRAY ||
            this.__code == TYPE_CODE_SHORT_ARRAY ||
            this.__code == TYPE_CODE_INT_ARRAY ||
            this.__code == TYPE_CODE_OBJECT_ARRAY ||
            this.__code == TYPE_CODE_BOOLEAN_ARRAY;
    }
        
    public is_ref(): boolean {
        return this.__code == TYPE_CODE_OBJECT_REF || this.is_array();

    }
}

let _JavaTypeDict: Map<string, JavaType> = new Map<string, JavaType>();
function _get_java_type(descriptor) {
    if (!_JavaTypeDict.has(descriptor)) {
        _JavaTypeDict.set(descriptor, new JavaType(descriptor));
    }
    return _JavaTypeDict.get(descriptor);
}


export class JcVariable {
    public _session: JcDebugger;
    public _name: string;
    public _type: JavaType;
    public _fields: JcVariable[];
    public _value: number;

    public constructor(session: JcDebugger, name: string, descriptor: string, value: number) {
        this._session = session;
        this._name = name;
        this._type = _get_java_type(descriptor);
        this._fields = null;
        if (this._type.code() == TYPE_CODE_BYTE ||
            this._type.code() == TYPE_CODE_BOOLEAN) {
            this._value = value & 0xff;
        }
        else if (this._type.is_ref() || this._type.code() == TYPE_CODE_SHORT) {
            this._value = value & 0xffff;
        }
        else
            this._value = value & 0xffffffff;
    }
    public name() {
        return this._name;
    }
    public type() {
        return this._type;
    }
    public fields(obj: IJcObject):JcVariable[] {
        if (!this._type.is_ref())
            throw ("this Jcvariable is not an array or object.");
        if (this._value == 0)
            throw ("cannot get fields of null object.");
        if (this._fields == null) {
            let fs = [];
            if (obj.type == OBJ_TYPE_OBJECT) {   // object
                let clsinfo: ClassInfo = this._session.jcDebugInfo.find_class_info(obj.aid, obj.clsoff);
                if (clsinfo == null)
                    return [];
                if (this._type.typename() != clsinfo.package_info.name + "/" + clsinfo.name)
                    this._type = _get_java_type(`L${clsinfo.package_info.name}/${clsinfo.name};`);

                let superlist = [clsinfo];
                while (clsinfo.superclass_name != "java/lang/Object") {
                    clsinfo = this._session.jcDebugInfo.find_class_info_by_name(clsinfo.superclass_name);
                    if (clsinfo == null)
                        break;
                    superlist.push(clsinfo);
                }
                let start = obj.fields.length;
                for (let clsinfo of superlist) {
                    let fcount = clsinfo.get_fields_count();
                    if (fcount == 0)
                        continue;
                    let index = start - fcount;
                    start = index;
                    if (index < 0)
                        break;
                    for (let fieldinfo of clsinfo.field_infos) {
                        if (fieldinfo.access_flags & ACC_STATIC)
                            continue;
                        if (fieldinfo.descriptor == "I") {
                            fs.push(new JcObjectField(this._session, fieldinfo.name, fieldinfo.descriptor, (obj.fields[index] << 16 + obj.fields[index + 1])));
                            index += 1;
                        }
                        else if (fieldinfo.descriptor == "B" || fieldinfo.descriptor == "Z") {
                            fs.push(new JcObjectField(this._session, fieldinfo.name, fieldinfo.descriptor, (obj.fields[index] >> 8)));
                        }
                        else {
                            fs.push(new JcObjectField(this._session, fieldinfo.name, fieldinfo.descriptor, (obj.fields[index])));
                        }
                        index += 1;
                    }
                }
            }
            else if (obj.type == OBJ_TYPE_REF) {// object array
                let clsinfo = this._session.jcDebugInfo.find_class_info(obj.aid, obj.clsoff);
                for (let i = 0; i < obj.fields.length; ++i) {
                    if (clsinfo == null)
                        fs.push(new JcArrayField(this._session, this._name, "Ljava/lang/Object;", obj.fields[i], i));
                    else
                        fs.push(new JcArrayField(this._session, this._name, "L" + clsinfo.package_info.name + "/" + clsinfo.name + ";", obj.fields[i], i));
                }
            }
            else {   // array
                let descriptor: string;
                if (obj.type == OBJ_TYPE_SHORT)
                    descriptor = "S";
                else if (obj.type == OBJ_TYPE_BYTE)
                    descriptor = "B";
                else if (obj.type == OBJ_TYPE_BOOLEAN)
                    descriptor = "Z";
                else if (obj.type == OBJ_TYPE_INT)
                    descriptor = "I";
                if (!strendswith(this._type.typename(), "[]")) {
                    this._type = _get_java_type("[" + descriptor);
                }
                for (let i = 0; i < obj.fields.length; ++i) {
                    fs.push(new JcArrayField(this._session, this.name(), descriptor, obj.fields[i], i));
                }
            }
            this._fields = fs;
            return this._fields;
        }
        else {
            return this._fields;
        }
    }
    public value() {
        return this._value;
    }

    public valstr() {
        let val = this._value;
        if(this.type().is_ref()){
            if(val == 0)
                return "null";
            else
                return sprintf("<ref=%d>", val);
        }
        let code = this.type().code();
        if(code == TYPE_CODE_BYTE){
            if(val >= 128)
                val -= 256;
            return sprintf("0x%02x (%d)", val&0xff, val);
        }
        else if(code == TYPE_CODE_BOOLEAN){
            if(val)
                return "true"; 
            else
                return "false";
        }
        else if(code == TYPE_CODE_SHORT){
            if(val >= 0x8000)
                val -= 0x10000;
            return sprintf("0x%04x (%d)", val&0xffff, val);
        }
        else if(code == TYPE_CODE_INT){
            if(val >= 0x80000000)
                val -= 0x10000000;
            return sprintf("0x%08x (%d)", val, val);
        }
        else{
            throw new Error("Jcvariable type error");
        }
    }

}

export class JcLocalVariable extends JcVariable {
    public constructor(session, name, descriptor, value) {
        super(session, name, descriptor, value);
    }
}

export class JcObjectField extends JcVariable {
    public constructor(session, name, descriptor, value) {
        super(session, name, descriptor, value);
    }
}

export class JcArrayField extends JcVariable {
    public constructor(session, name, descriptor, value, index) {
        super(session, `${name}[${index}]`, descriptor, value);
    }
}

export class JcStaticField extends JcVariable {
    public _pkgaid: string;
    public _offset: number;
    public _value: number;

    public constructor(session, name, descriptor, pkgaid, offset, value) {
        super(session, name, descriptor, 0);
        this._pkgaid = pkgaid;
        this._offset = offset;
        this._value = value;
    }
}

export class JcConstantField extends JcVariable{
    public constructor(session, name, descriptor, value){
        super(session, name, descriptor, value);
    }
}



